﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SpiceNet.Circuits;
using SpiceNet.Diagnostics;
using System.Numerics;

namespace SpiceNet.Simulations
{
    public class AC : Analysis
    {
        /// <summary>
        /// The type of steps that can be taken
        /// </summary>
        public enum StepTypes { Decade = 1, Octave = 2, Linear = 3 }

        /// <summary>
        /// Constructor
        /// </summary>
        public AC() : base(typeof(AcJob))
        {
            Name = "AC";
            Description = "A.C. Small signal analysis";
            Domain = DomainTypes.FrequencyDomain;
            DoIc = true;
        }

        /// <summary>
        /// Wrapper for ACan in acan.c
        /// Perform AC analysis
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="reset"></param>
        public override void Func(Circuit ckt, bool reset)
        {
            // Get the state for AC simulation
            CircuitStateComplex state = ckt.StateComplex;

            AcJob curjob = ckt.CurrentJob as AcJob;
            double freq = 0.0, freqTol = 0.0;
            int ifreq = 0;

            if (curjob.ACsaveFreq == 0 || reset)
            {
                if (curjob.ACnumberSteps < 1)
                    curjob.ACnumberSteps = 1;

                switch (curjob.ACstepType)
                {
                    case StepTypes.Decade:
                        curjob.ACfreqDelta = Math.Exp(Math.Log(10.0) / curjob.ACnumberSteps);
                        break;

                    case StepTypes.Octave:
                        curjob.ACfreqDelta = Math.Exp(Math.Log(2.0) / curjob.ACnumberSteps);
                        break;

                    case StepTypes.Linear:
                        if (curjob.ACnumberSteps - 1 > 1)
                            curjob.ACfreqDelta = (curjob.ACstopFreq - curjob.ACstartFreq) / (curjob.ACnumberSteps - 1);
                        else
                            curjob.ACfreqDelta = Double.PositiveInfinity;
                        break;

                    default:
                        throw new CircuitException("Invalid step type");
                }

                // Calculate the operating point
                Op(ckt,
                    (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.DcOp | Circuit.Modes.InitJct,
                    (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.DcOp | Circuit.Modes.InitFloat,
                    ckt.Config.DcMaxIterations);

                // Load once more in order to calculate the AC info for all devices
                ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.DcOp | Circuit.Modes.InitSmSig;
                Load(ckt);

                // Starting frequency
                freq = curjob.ACstartFreq;
            }
            else
            {
                freq = curjob.ACsaveFreq;
                curjob.ACsaveFreq = 0.0; // Clear the old frequency
            }

            // Get the frequency tolerance
            switch (curjob.ACstepType)
            {
                case StepTypes.Decade:
                case StepTypes.Octave:
                    freqTol = curjob.ACfreqDelta * curjob.ACstopFreq * ckt.Config.RelTol;
                    break;

                case StepTypes.Linear:
                    freqTol = curjob.ACfreqDelta * ckt.Config.RelTol;
                    break;
            }

            // Initialize the complex state
            ckt.StateComplex.Init(ckt);

            // Iterate
            while (freq <= curjob.ACstopFreq + freqTol)
            {
                // Walk along the imaginary axis
                state.Omega = new Complex(0.0, 2.0 * Circuit.CONSTPI * freq);
                ckt.Mode = (ckt.Mode & Circuit.Modes.Uic) | Circuit.Modes.Ac;

                // Do AC iteration
                AcIterate(ckt);

                // Dump circuit
                Dump(ckt);

                // Increment frequency
                switch (curjob.ACstepType)
                {
                    case StepTypes.Decade:
                    case StepTypes.Octave:
                        freq *= curjob.ACfreqDelta;
                        if (curjob.ACfreqDelta == 1.0)
                            return;
                        break;

                    case StepTypes.Linear:
                        ifreq++;
                        freq = curjob.ACstartFreq + ifreq * curjob.ACfreqDelta;
                        if (curjob.ACfreqDelta == 0.0)
                            return;
                        break;
                }
            }
        }

        /// <summary>
        /// Load AC version
        /// </summary>
        public void AcLoad(Circuit ckt)
        {
            // Clear the state
            ckt.StateComplex.Matrix.Clear();
            ckt.StateComplex.Rhs.Clear();

            // Iterate through all models
            foreach (Type t in ckt.ModelTypes)
            {
                var models = ckt[t];
                for (int i = 0; i < models.Length; i++)
                    models[i].AcLoad(ckt);
            }
        }

        /// <summary>
        /// Iterate for complex analysis
        /// </summary>
        public void AcIterate(Circuit ckt)
        {
            // retry:
            ckt.IsCon = true;

            // Load AC
            AcLoad(ckt);

            // Solve
            // OldSolution = Solution;
            ckt.StateComplex.Solve();
        }
    }
}
